Table Of Contents

Previous topic

CombPotential class

Next topic

HybridCalculator class

This Page

ChargeRelaxation class

This class controls equilibration of atomic charges in the system.

It is possible for the user to define the charges of atoms in ASE. If a system exhibits charge transfer, polarization, charged defects etc., one may not know the charges beforehand or the charges may change dynamically during simulation. To handle such systems, it is possible to let the charges in the system develop dynamically.

Since charge dynamics are usually much faster than dynamics of the ions, it is usually reasonable to allow the charges to equilibrate between ionic steps. This does not conserve energy exactly, however, since the charge equilibration drives the system charge distribution towards a lower energy. The energy change in charge redistribution is lost unless it is fed back to the system.

Connecting the structure, calculator and relaxation algorithm

Special care must be taken when setting up links between the atomic structure (ASE Atoms), the calculator (Pysic), and the charge relaxation algorithm (ChargeRelaxation). While some of the objects must know the others, in some cases the behavior of the simulator changes depending on whether or not they have access to the other objects.

The atoms and the calculator are linked as required in the ASE calculator interface: One can link the two by either the set_atoms() method of Pysic, or the set_calculator method of ASE Atoms. In either case, the atomic structure is given a link to the calculator, and a copy of the structure is stored in the calculator. This must be done in order to do any calculations on the system.

Also the relaxation algorithm has to know the Pysic calculator, since the relaxation is done according to the Potential interactions stored in the calculator. The algorithm can be made to know the calculator via the set_calculator() method of ChargeRelaxation. By default, this does not make the calculator know the relaxation algorithm, however. Only if the optional argument reciprocal=True is given the backwards link is also made. Pysic can be made to know the relaxation algorithm also by calling the method set_charge_relaxation(). Unlike the opposite case, by making the link from the calculator, the backwards link from the relaxation algorithm is always made automatically. In fact, even though linking an algorithm to a calculator does not automatically link the calculator to the algorithm, if a different calculator was linked to the algorithm, the link is automatically removed.

This slightly complicated behavior is summarized as follows: The algorithm should always have a link to a calculator, but a calculator need not have a link to an algorithm. If a calculator does link to an algorithm, the algorithm must link back to the same calculator. Clearly one does not always want to perform charge relaxation on the system and so it makes sense that the calculator need not have a link to a charge relaxation algorithm. If such a link does exist, then the relaxation is automatically invoked prior to each energy and force evaluation. This is necessary in simulations such as molecular dynamics (MD). A charge relaxation can be linked to a calculator in order to do charge equilibration, but if one does not wish to trigger the charge relaxation automatically, then it is enough to just not let the calculator know the relaxation algorithm.

The atomic structure cannot be given a link to the relaxation algorithm since the charge relaxation is not part of the ASE API and so the atoms object does not know how to interact with it. In essence, from the point of view of the structure, the charge relaxation is fully contained in the calculator.

The charge relaxation algorithm always acts on the structure contained in the calculator. The atomic charges of this structure are automatically updated during the relaxation. Since the calculator only stores a copy of the original structure, the original is not updated. This may be desired if, for instance, one wishes to revert back to the original charges. However, during strucural dynamics simulations such as MD, it is necessary that the relaxed charges are saved between structural steps. This is a problem, since structural dynamics are handled by ASE, and ASE invokes the calculation of forces with the original ASE Atoms object. Therefore, if the relaxed charges are not saved, charge relaxation is always started from the original charges, which may be very inefficient. In order to have also the original structure updated automatically, the charge relaxation can be made to know the original structure with set_atoms(). Note that the structure given to the algorithm is not used in the actual relaxation; the algorithm always works on the structure in the calculator, which may be different. The given structure is merely updated according to the calculation results.

Piping ChargeRelaxation algorithms

Sometimes the most efficient way to optimize a system is to combine several algorithms or several parameterizations of a single algorithm in a sequence. First, one can run a robust but less efficient algorithm to find a rough solution and follow with a more efficient method to pinpoint the optimum at high precision. For a plain relaxation, this can be done manually. However, if the optimization needs to happen automatically, e.g., between molecular dynamics steps, one needs to combine several ChargeRelaxation objects together. This can be done through the method add_relaxation_pipe().

Adding a piped ChargeRelaxation object simply calls the charge_relaxation() methods of both objects one after another when relaxation is invoked. Several objects can be piped by recursive piping. For instance, the following:

rel1 = ChargeRelaxation(...)
rel2 = ChargeRelaxation(...)
rel3 = ChargeRelaxation(...)

rel1.add_relaxation_pipe(rel2)
rel2.add_relaxation_pipe(rel3)

creates a pipe rel1-rel2-rel3, where rel1 is executed first and rel3 last. Creating looped pipes where the same relaxation appears twice is not allowed.

By default, when a pipe is created, the calculator and structure objects of the first relaxation are copied also to all the piped ChargeRelaxation objects (including recursively defined ones).

Observers

Similarly to ASE molecular dynamics, also the ChargeRelaxation allows one to attach callback functions to the dynamics. That is, one can have a specific functions be automatically invoked at given intervals. This can be used for instance for printing statistics or saving data during the optimization run. For instance, the following example saves and plots the charge of the atom with index 0:

system = Atoms(...)
calc = Pysic(...)
rel = ChargeRelaxation(calculator = calc, system = system)

charge0 = []
def save_charge():
    charge0.append(calc.get_atoms().get_charges()[0])

rel.add_observer(save_charge)
rel.charge_relaxation()

import matplotlib.pyplot as plt
plt.plot(np.array(range(len(charge0))), thecharges)
plt.show()

List of currently available relaxation methods

Below is a list of the charge relaxation methods currently implemented.

Damped dynamics

Assigning an inertia, \(M_q\), on the atomic charges, \(q_i\), we can describe the system with the Lagrangian

\[L = \sum_i \frac{1}{2} m_i \dot{\mathbf{r}}_i^2 + \sum_i \frac{1}{2} M_q \dot{q}_i^2 - U(\{q\},\{\mathbf{r}\}) - \nu \sum_i q_i,\]

where \(m_i, \mathbf{r}_i\) are the mass and position of atom \(i\), respectively. The last term is a Lagrange multiplier corresponding to the constraint of fixed total charge, i.e., \(\sum_i q_i = Q_\mathrm{tot}\) being constant. The total potential energy \(U\) is a function of all charges and positions.

The equations of motion for this system are

\[\begin{eqnarray} m_i \ddot{\mathbf{r}}_i & = & -\nabla_i U \\ M_q \ddot{q}_i & = & -\frac{\partial U}{\partial q_i} - \nu. \end{eqnarray}\]

In the charge equation, the Lagrange multiplier can be shown to equal the average electronegativity of the system, \(\nu = \bar{\chi}\), and the derivative is the effective electronegativity of atom \(i\), \(-\frac{\partial U}{\partial q_i} = \chi_i\). Thus, the effective force driving the change in atomic charges is the electronegativity difference from the avarage

\[M_q \ddot{q}_i = -\frac{\partial U}{\partial q_i} - \nu = \chi_i - \bar{\chi} = \Delta \chi_i.\]

In the damped dynamic equilibration, the charges are developed dynamically according to the equation of motion with an added damping (friction) term \(- \eta \dot{q}_i\)

(1)\[M_q \ddot{q}_i = \Delta \chi_i - \eta \dot{q}_i.\]

This leads to the charges being driven towards a state where the driving force vanishes \(\Delta \chi_i = 0\), i.e., the electronegativities are equal.

During simulation such as molecular dynamics or geometry optimization, charge equilibration is done by running the damped charge dynamics (1) before each force or energy evaluation.

Keywords:

>>> names_of_parameters('dynamic')
['n_steps', 'timestep', 'inertia', 'friction', 'tolerance']

Potentiostat

Similarly to Damped dynamics, a potentiostat drives the atomic charges \(q_i\) with the dynamics

\[\begin{eqnarray} m_i \ddot{\mathbf{r}}_i & = & -\nabla_i U \\ M_q \ddot{q}_i & = & -\frac{\partial U}{\partial q_i} - \Phi. \end{eqnarray}\]

Charge equilibration tries to optimize the charge distribution (with fixed total charge) so that the total energy is minimized. The potentiostat, however, connects the system to an electrode (a charge resevoir) at constant potential \(\Phi\). This means that the total charge of the system is allowed to change so that the electronegativity of each atom equals the external potential \(\chi_i = -\frac{\partial U}{\partial q_i} = \Phi\).

Keywords:

>>> names_of_parameters('potentiostat')
['n_steps', 'timestep', 'inertia', 'potential']

Optimization

This charge optimization method uses the Scipy fmin_slsqp function for constrained optimization using Sequential Least Squares Programming (SLSQP).

Instead of dynamic simulation, the algorithm searches for energy minimum \(U(\mathbf{q})\) with the constraint of constant charge \(\sum_i q_i = Q\).

As the external function does not support callback functions, observers cannot be attached to this algorthm.

Keywords:

>>> names_of_parameters('optimize')
['n_steps', 'tolerance']

Full documentation of the ChargeRelaxation class

class pysic.charges.relaxation.ChargeRelaxation(relaxation='dynamic', calculator=None, parameters=None, atoms=None)[source]

A class for handling charge dynamics and relaxation.

Pysic does not implement molecular dynamics or geometric optimization since they are handled by ASE. Conceptually, the structural dynamics of the system are properties of the atomic geometry and so it makes sense that they are handled by ASE, which defines the atomic structure in the first place, in the ASE Atoms class.

On the other hand, charge dynamics are related to the electronic structure of the system. Since ASE is meant to use methods such as density functional theory (DFT) in the calculators is employs, all electronic properties are left at the responsibilty of the calculator. This makes sense since in DFT the electron density is needed for calculations of forces and energies.

Pysic is not a DFT calculator and there is no electron density but the atomic charges can be made to develop dynamically. The ChargeRelaxation class handles these dynamics.

Parameters:

relaxation: string
a keyword specifying the mode of charge relaxation
calculator: Pysic object
a Pysic calculator
parameters: list of doubles
numeric values for parameters
atoms: ASE Atoms object
The system whose charges are to be relaxed. Note! The relaxation is always done using the atoms copy in Pysic, but if the original structure needs to be updated as well, the relaxation algorithm must have access to it.
add_observer(function, interval=1, *args, **kwargs)

Attach a callback function.

Call the given function with the given arguments at constant intervals, after a given number of relaxation steps.

Parameters:

function: Python function
the function to be called
interval: integer
the number of steps between callback
args: string
arguments for the callback function
kwargs: string
keyword arguments for the callback function
call_observers(step)

Calls all the attached callback functions.

Parameters:

step: integer
the number of dynamics steps taken during the relaxation
charge_relaxation()[source]

Performs the charge relaxation.

The relaxation is always performed on the system associated with the Pysic calculator joint with this ChargeRelaxation. The calculated equilibrium charges are returned as a numeric array.

If an ASE Atoms structure is known by the ChargeRelaxation (given through set_atoms()), the charges of the structure are updated according to the calculation result. If the structure is not known, the charges are updated in the structure stored in the Pysic calculator, but not in any other object. Since Pysic only stores a copy of the structure it is given, the original ASE Atoms object will not be updated.

clear_observers()

Removes all observers.

clear_relaxation_pipe()

Equivalent to set_relaxation_pipe(None).

get_atoms()[source]

Returns the atoms object known by the algorithm.

This is the ASE Atoms which will be automatically updated when charge relaxation is invoked.

get_calculator()[source]

Returns the Pysic calculator assigned to this ChargeRelaxation.

get_parameters()[source]

Returns a list containing the numeric values of the parameters.

get_relaxation()[source]

Returns the keyword specifying the mode of relaxation.

get_relaxation_pipe(recursive=True)

Return the piped ChargeRelaxation objects.

By default, a list is given containing all the ChargeRelaxation objects in the pipeline (or an empty list).

If called with the argument False, only the ChargeRelaxation next-in-line will be returned (or None).

Parameters:

recursive: boolean
If True, will return a list containing all the piped objects. Otherwise, only the next-in-line is returned.
initialize_parameters()[source]

Creates a dictionary of parameters and initializes all values to 0.0.

relaxation_modes = ['dynamic', 'potentiostat', 'optimize']

Names of the charge relaxation algorithms available.

These are keywords needed when creating the ChargeRelaxation objects as type specifiers.

relaxation_parameter_descriptions = {'dynamic': ['number of time steps of charge dynamics between molecular dynamics', 'time step of charge dynamics', 'fictional charge mass', 'friction coefficient for damped charge dynamics', 'convergence tolerance'], 'optimize': ['maximum number of optimization steps', 'convergence tolerance'], 'potentiostat': ['number of time steps of charge dynamics between molecular dynamics', 'time step of charge dynamics', 'fictional charge mass', 'external potential']}

Short descriptions of the relaxation parameters.

relaxation_parameters = {'dynamic': ['n_steps', 'timestep', 'inertia', 'friction', 'tolerance'], 'optimize': ['n_steps', 'tolerance'], 'potentiostat': ['n_steps', 'timestep', 'inertia', 'potential']}

Names of the parameters of the charge relaxation algorithms.

set_atoms(atoms, pass_to_calculator=False)[source]

Lets the relaxation algorithm know the atomic structure to be updated.

The relaxation algorithm always works with the structure stored in the Pysic calculator it knows. If pass_to_calculator = True, this method also updates the structure known by the calculator. However, this is not the main purpose of letting the ChargeRelaxation know the structure - it is not even necessary that the structure known by the relaxation algorithm is the same as that known by the calculator.

The structure given to the algorithm is the structure whose charges it automatically updates after relaxing the charges in charge_relaxation(). In other words, if no structure is given, the relaxation will update the charges in the strucure known by Pysic, but this is always just a copy and so the original structure is left untouched.

Parameters:

atoms: ASE Atoms object
The system whose charges are to be relaxed. Note! The relaxation is always done using the atoms copy in Pysic, but if the original structure needs to be updated as well, the relaxation algorithm must have access to it.
pass_to_calculator: logical
if True, the atoms are also set for the calculator via set_atoms()
set_calculator(calculator, reciprocal=False)[source]

Assigns a Pysic calculator.

The calculator is necessary for calculation of electronegativities. It is also possible to automatically assign the charge relaxation method to the calculator by setting reciprocal = True.

Note though that it does make a difference whether the calculator knows the charge relaxation or not: If the Pysic has a connection to the ChargeRelaxation, every time force or energy calculations are requested the charges are first relaxed by automatically invoking charge_relaxation(). If there is no link, it is up to the user to start the relaxation.

Parameters:

calculator: Pysic object
a Pysic calculator
reciprocal: logical
if True, also the ChargeRelaxation is passed to the Pysic through set_charge_relaxation().
set_parameter_value(parameter_name, value)[source]

Sets a given parameter to the desired value.

Parameters:

parameter_name: string
name of the parameter
value: double
the new value of the parameter
set_parameter_values(parameters)[source]

Sets the numeric values for all parameters.

Use get_parameters() to see the correct order of values.

Parameters:

parameters: list of doubles
list of values to be assigned to parameters
set_parameters(parameters)[source]

Sets the numeric values for all parameters.

Equivalent to set_parameter_values()

Parameters:

parameters: list of doubles
list of values to be assigned to parameters
set_relaxation(relaxation)[source]

Sets the relaxation method.

The method also creates a dictionary of parameters initialized to 0.0 by invoking initialize_parameters().

Parameters:

relaxation: string
a keyword specifying the mode of charge relaxation
set_relaxation_pipe(relaxation, pass_info=True)

Add another ChargeRelaxation to take place immediately after this relaxation.

Sometimes it is most efficient to start the relaxation with a robust but possibly slow algorithm and switch to a more advanced one once closer to equilibrium. On the other hand, when relaxation is needed between MD steps, this is not possible manually. Therefore, one can link several ChargeRelaxation objects together to automatically invoke a sequential relaxation procedure.

You should only give one ChargeRelaxation for piping. If you want to pipe more than two relaxation sequences together, you should always link the additional relaxation to the currently final ChargeRelaxation object to create a nested pipeline:

rel1 = ChargeRelaxation()
rel2 = ChargeRelaxation()
rel3 = ChargeRelaxation()

rel1.set_relaxation_pipe(rel2)
rel2.set_relaxation_pipe(rel3)

rel1.charge_relaxation() # will do rel1 -> rel2 -> rel3

Parameters:

relaxation: ChargeRelaxation object
the relaxation to be piped after this one
pass_info: boolean
If True, the calculator and atoms of this calculator will be automatically copied over to the piped calculator.